/// A simple sum of two vectors in GLSL-GPGPU
/// by Alvaro Cuno: alvaroecp@gmail.com

#include "GLee.h"
#include <GL/glut.h>
#include <iostream>
#include <vector>
#include <iterator>

#include "fboT.h"
#include "glslKernel.h"

using namespace std;

typedef float Number;
const int texSize = 4; // texture size
const int n = texSize*texSize; // size of vectors

FBO<1>         fbo;        // the frame buffer object 
glslKernel     sumKernel;  // the sum kernel

/// Executes R = A + B using the graphics hardware
void sumVectors(const vector<Number> &A, const vector<Number> &B, vector<Number> &R);
/// Performs the GLSL kernel setup
void fsKernelSetup();
/// Performs the FBO setup (key for off-screen rendering - gpgpu)
void fboSetup();
/// Sends the data to the GPU memory
GLuint uploadArray(const vector<Number> &array);
/// Gets data from GPU memory
void downloadArray(vector<Number> &array);
/// Executes the glsl shader
void runShader(GLuint texAId, GLuint texBId);

/// The main function
int main (int argc, char** argv) {
   
   // OpenGl context creation
   glutInit(&argc, argv);
   GLuint winHandle = glutCreateWindow("GPGPU vectors sum");
   
   // Input data
   vector<Number> arrayA(n);
   vector<Number> arrayB(n);
   vector<Number> arrayR(n);
   for (int i=0; i<arrayA.size(); ++i) arrayA[i]=rand()/(Number)RAND_MAX;
   for (int i=0; i<arrayB.size(); ++i) arrayB[i]=rand()/(Number)RAND_MAX;
   cout<<"The input arrays are: "<<endl;
   copy(arrayA.begin(), arrayA.end(), ostream_iterator<Number>(cout, " ")); cout<<endl<<endl;
   copy(arrayB.begin(), arrayB.end(), ostream_iterator<Number>(cout, " ")); cout<<endl<<endl;
   
   // Sum the vectors
   sumVectors(arrayA, arrayB, arrayR);
   
   cout<<"The result is: "<<endl;
   copy(arrayR.begin(), arrayR.end(), ostream_iterator<Number>(cout, " ")); cout<<endl;
   
   glutDestroyWindow(winHandle);
   return 0;
}

/// Executes R = A + B using the graphics hardware
/// @param A an input vector
/// @param B an input vector
/// @param R the result
void sumVectors(const vector<Number> &A, const vector<Number> &B, vector<Number> &R) {

   GLuint arrayAid = uploadArray(A);
   GLuint arrayBid = uploadArray(B);
   runShader(arrayAid, arrayBid);
   downloadArray(R);
   fbo.off(); // turn-off the fbo only after to read the data
}

/// Sends the data to the GPU memory
GLuint uploadArray(const vector<Number> &array) {

   /// Makes a texture
   GLuint texId; // texture identifier
   glGenTextures(1, &texId);
   
   /// Allocates texture memory for storing the data
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texId);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_R32_NV, texSize, texSize, 
                0, GL_RED, GL_FLOAT, &array[0]);
   return texId;
}

void downloadArray(vector<Number> &array) {

   glReadPixels(0, 0, texSize, texSize, GL_RED, GL_FLOAT, &array[0]);
   glFinish();
}

void runShader(GLuint texAId, GLuint texBId) {
   
   fboSetup();
   fbo.on();
   fsKernelSetup();
   
   sumKernel.use(true);
   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texAId);
   sumKernel.set_uniform("arrayA", 0);
   glActiveTexture(GL_TEXTURE1);
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texBId);
   sumKernel.set_uniform("arrayB", 1);
   
   // Fragments generation
   glBegin(GL_QUADS);
     glVertex2f(0, 0);
     glVertex2f(texSize, 0);
     glVertex2f(texSize, texSize);
     glVertex2f(0, texSize);
   glEnd();
   glFinish();
   
   sumKernel.use(false);
}

/// Performs the GLSL kernel setup
void fsKernelSetup() {
   
   sumKernel.fragment_source("sum.fs");
   sumKernel.install(true);
}

/// Performs the FBO setup (key for off-screen rendering - gpgpu)
void fboSetup() {
   
   fbo.setNumberOfChannels(1); // set the number of channels(RGBA): only one in this example
   fbo.initFBO(texSize, texSize); // FBO initialization
}

